package club.jdiy.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * MD5加密工具类.
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20200517
 */
@SuppressWarnings("ALL")
public final class Md5Utils {
    private Md5Utils() {
    }

    /**
     * 直接MD5加密（32位小写）.<br/>
     * 注：如果您想获取16位长度的md5,将此密文进行8-24位字符截取即可。
     * 即：<br/>Md5Utils.md5(str).substring(8,24);
     *
     * @param str 要加密的明文字符.
     * @return 32位密文(小写)
     */
    public static String md5(String str) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(str.getBytes());
            byte[] b = md.digest();
            int i;
            StringBuilder buf = new StringBuilder();
            for (byte aB : b) {
                i = aB;
                if (i < 0) i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            return buf.toString().toLowerCase();
        } catch (NoSuchAlgorithmException e) {
            throw new NullPointerException(e.getMessage());
        }
    }

    /**
     * JDiy实现的一个md5加盐算法.
     * 同一个字符串，多次调用此方法将得到不同的密文.
     * 通过md5加盐，可使用户的简洁密码无法通过暴力破解.
     *
     * @param str 要加密的明文字符.
     * @return 40位定长密文
     */
    public static String encrypt(String str) {
        SecureRandom o = new SecureRandom();
        char[] c = new char[8];
        for (int i = 0; i < 8; i++) c[i] = HEX_DIGITS[o.nextInt(HEX_DIGITS.length)];
        String s1 = md5(str + String.valueOf(c));
        StringBuilder sb = new StringBuilder(40);
        for (int i = 0, j = 0, k = 0; i < s1.length(); i++, k++) {
            if (j == k && j < c.length) {
                sb.append(c[j++]);
                k = 0;
            }
            sb.append(s1.charAt(i));
        }
        return sb.toString();
    }


    /**
     * md5验证.<br/>
     * <br>示例1(普通md5加密与验证)：<br/><br/>
     * String str = "123456"; <br/>
     * String sec = Md5Utils.md5(str); <br/>
     * System.out.println(str+" 加密后的密文为："+sec);<br/>
     * System.out.println("验证(true表示通过)："+Md5Utils.validate(str,sec));<br/>
     * <br>示例2(加盐算法加密与验证)：<br/><br/>
     * String str = "123456"; <br/>
     * String sec = Md5Utils.encrypt(str);  //同一明文，每次调用返回的密文都不会一样<br/>
     * System.out.println(str+" 加密后的密文为："+sec);<br/>
     * System.out.println("验证(true表示通过)："+Md5Utils.validate(str,sec));<br/>
     *
     * @param str    明文
     * @param secStr 密文(32位普通md5或者JDiy加盐算法生成的40位定长密文都可以通过此方法进行验证)
     * @return 验证成功返回true
     */
    public static boolean validate(String str, String secStr) {
        if (secStr == null || str == null) return false;
        else if (secStr.length() == 32) return secStr.equalsIgnoreCase(md5(str));
        else if (secStr.length() == 16) return secStr.equalsIgnoreCase(md5(str).substring(8, 24));
        else if (secStr.length() != 40) return false;
        char[] a = new char[8], b = new char[32];
        for (int i = 0, j = 0, k = 0, l = 0, m = 0; i < secStr.length(); i++, k++) {
            if (j == k) {
                a[l++] = secStr.charAt(i++);
                k = 0;
                j++;
            }
            b[m++] = secStr.charAt(i);
        }
        return md5(str + String.valueOf(a)).equalsIgnoreCase(String.valueOf(b));
    }

    public static void main(String[] args) {
        String s1 = "123456", s2;
        System.out.println(s2 = encrypt(s1));
        System.out.println(validate(s1, s2));
    }

    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
}
